#import "ImageCache.h"


@implementation UIImageViewCached

-(void)awakeFromNib {
    [super awakeFromNib];
    empty_image = self.image;
}

-(void)update {
    if( url_image.get() )
        self.image = url_image.get();
    else if( thumbnail_image.get() )
        self.image = thumbnail_image.get();
    else
        self.image = empty_image.get();
}
-(void)imageCache:(ImageCache *)cache image:(UIImage *)image statusChanged:(ImageCacheEntryStatus)status forUrl:(NSURL *)url_ {
    if( url.get() && [url.get() isEqual:url_] )
        url_image = image;
    if( thumbnail_url.get() && [thumbnail_url.get() isEqual:url_] )
        thumbnail_image = image;
    [self update];
}

-(void)dealloc {
    [self cancelLoad];
    [super dealloc];
}

-(void)cancelLoad {
    if( url.get() )
        [[ImageCache instance] stopLoadingImageforUrl:url.get() withDelegate:self];
    if( thumbnail_url.get() )
        [[ImageCache instance] stopLoadingImageforUrl:thumbnail_url.get() withDelegate:self];
}

-(void)setURL:(NSURL *)url_ {
    [self cancelLoad];
    thumbnail_url = 0;
    thumbnail_image = 0;
    url = url_;
    url_image = [[ImageCache instance] startLoadingImageforUrl:url.get() withDelegate:self];
    [self update];
}

-(void)setURL:(NSURL *)url_ thumbnailURL:(NSURL *)thumbnail_url_ {
    [self cancelLoad];
    url = url_;
    thumbnail_url = thumbnail_url_;
    url_image = [[ImageCache instance] startLoadingImageforUrl:url.get() withDelegate:self];;
    thumbnail_image = [[ImageCache instance] startLoadingImageforUrl:thumbnail_url.get() withDelegate:self];;
    [self update];
}

@end

static ImageCache * image_cache_instance = 0;

@implementation ImageCache

-(id)initWithClearInterval:(NSTimeInterval)clear_interval_seconds {
    if ( (self = [super init]) ) {
        if( image_cache_instance )
            [NSException raise:@"You should not create second image instance" format:@""];
        load_save_queue = [[[NSOperationQueue alloc] init] autorelease];
        [load_save_queue.get() setMaxConcurrentOperationCount:1];
		[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning)
                                                     name:UIApplicationDidReceiveMemoryWarningNotification
                                                   object:nil];
        image_cache_instance = self;
    }
    return self;
}

-(void)dealloc {
    image_cache_instance = 0;
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [load_save_queue.get() cancelAllOperations];

    [super dealloc];
}

+(ImageCache *)instance {
    return image_cache_instance;
}

-(void)didReceiveMemoryWarning {
	CacheType::iterator it = memory_cache.begin();
	while( it != memory_cache.end() )
	{
		if( it->second.status == LOADED && it->second.delegates.empty() )
		{
			NSLog(@"Freeing image with retainCount=%d", [it->second.image.get() retainCount]);
			memory_cache.erase(it++);
		}
		else
			++it;
	}
}


-(NSString *)nameForUrl:(NSURL *)url {
	return calculate_md5([url absoluteString]);
}


-(NSString *)pathForUrl:(NSURL *)url old:(NSString **)old_path {
	NSString * name = [self nameForUrl:url];
	*old_path = [cache_path_old.get() stringByAppendingPathComponent:name];
	return [cache_path_current.get() stringByAppendingPathComponent:name];
}

-(void)downloadNext {
}

-(void)broadcastImageChanged:(ImageCacheEntry *)entry forUrl:(NSURL *)url {
    std::for_each(entry->delegates.begin(), entry->delegates.end(), ^(id<ImageCacheDelegate> del) {
		[del imageCache:self image:entry->image.get() statusChanged:entry->status forUrl:url];
    });
}

-(void)imageLoadedFromDisk:(NSArray *)arr {
	NSURL * url = [arr objectAtIndex:0];
	UIImage * image = ([arr count] < 2) ? 0 : [arr objectAtIndex:1]; // image was nil, did not decode from disk or from network

	ImageCacheEntry & ss = memory_cache[url];
	ss.image = image;
	ss.status = LOADED;
	[self broadcastImageChanged:&ss forUrl:url];
}


-(UIImage *)startLoadingImageforUrl:(NSURL *)url withDelegate:(id<ImageCacheDelegate>)dg {
	if( !url )
		return 0;
	ImageCacheEntry & ss = memory_cache[url];
	if( dg )
		ss.delegates.push_back(dg);
	if( ss.status != JUST_CREATED )
	{
		return ss.image.get();
	}
    if( ![url isFileURL] )
    {
        NSString * old_path = 0;
        NSString * path = [self pathForUrl:url old:&old_path];
        if( ![[NSFileManager defaultManager] fileExistsAtPath:path] && ![[NSFileManager defaultManager] fileExistsAtPath:old_path] )
        {
//            if( LOG )
//                NSLog(@"startLoadingImageforUrl will download {%@}", url);
            ss.status = DOWNLOADING;
            waiting_download.push_back( url );
            [self downloadNext];
            return 0;
        }
        ss.status = LOADING;
        NSOperation * op = [NSBlockOperation blockOperationWithBlock:^(){
            NSError * error = 0;
            [[NSFileManager defaultManager] moveItemAtPath:old_path toPath:path error:&error]; // Do not test for failure
            UIImage * image = [UIImage imageWithContentsOfFile:path];
            NSArray * arr = [NSArray arrayWithObjects:url, image, nil];
            [self performSelectorOnMainThread:@selector(imageLoadedFromDisk:) withObject:arr waitUntilDone:YES];
        }];
        [op setThreadPriority:0.01f];
        [load_save_queue.get() addOperation:op];
        return 0;
    }
    ss.status = LOADING;
    NSOperation * op = [NSBlockOperation blockOperationWithBlock:^(){
        UIImage * image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
        NSArray * arr = [NSArray arrayWithObjects:url, image, nil];
        [self performSelectorOnMainThread:@selector(imageLoadedFromDisk:) withObject:arr waitUntilDone:YES];
    }];
    [op setThreadPriority:0.01f];
    [load_save_queue.get() addOperation:op];
	return 0;
}

-(void)stopLoadingImageforUrl:(NSURL *)url withDelegate:(id<ImageCacheDelegate>)dg {
	if( !url )
		return;
	CacheType::iterator it = memory_cache.find(url);
	if( it == memory_cache.end() )
		return;
	it->second.delegates.remove(dg);
}

@end

/*static const bool LOG = false;

@implementation ImageCache

@synthesize delegate;

-(id)initWithClearInterval:(NSTimeInterval)clear_interval_seconds {
    if ( self = [super init]) {
		NSError * error = 0;
		NSString * documents_dir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
		cache_path_current = [documents_dir stringByAppendingPathComponent:@"ImageCache"];
		cache_path_old = [documents_dir stringByAppendingPathComponent:@"ImageCacheOld"];

		NSDate * clear_date = objective_c_cast<NSDate>( [[NSUserDefaults standardUserDefaults] objectForKey:@"image_cache_clear_date"] );
		if( !clear_date )
		{
			clear_date = [NSDate date];
			[[NSUserDefaults standardUserDefaults] setObject:clear_date forKey:@"image_cache_clear_date"];
		}
		NSTimeInterval interval = [clear_date timeIntervalSinceNow];
		NSLog(@"ImageCache %lf seconds since last clear", interval);
		if( interval < -clear_interval_seconds )
		{
			NSLog(@"ImageCache attempt to clear cache");
			[[NSFileManager defaultManager] removeItemAtPath:cache_path_old.get() error:&error]; // Do not check for failure
			if( [[NSFileManager defaultManager] moveItemAtPath:cache_path_current.get() toPath:cache_path_old.get() error:&error] )
			{
				NSLog(@"ImageCache cleare cache successfull");
				clear_date = [NSDate date];
				[[NSUserDefaults standardUserDefaults] setObject:clear_date forKey:@"image_cache_clear_date"];
			}
		}

		[[NSFileManager defaultManager] createDirectoryAtPath:cache_path_current.get() withIntermediateDirectories:YES attributes:nil error:&error]; // Do not check for failure

		queueLock = [[[NSConditionLock alloc] initWithCondition:0] autorelease];
		worker = [[[NSThread alloc] initWithTarget:self selector:@selector(workerThread:) object:nil] autorelease];
		[worker.get() start];
		
		passive_downloaders.push_back( [[[ImageCacheDownloader alloc] initWithImageCache:self name:@"Vasya"] autorelease] );
		passive_downloaders.push_back( [[[ImageCacheDownloader alloc] initWithImageCache:self name:@"Petya"] autorelease] );
		passive_downloaders.push_back( [[[ImageCacheDownloader alloc] initWithImageCache:self name:@"Lesha"] autorelease] );
		passive_downloaders.push_back( [[[ImageCacheDownloader alloc] initWithImageCache:self name:@"Slava"] autorelease] );
	
    }
    return self;
}


- (void)workerThread:(id)data
{
	[NSThread setThreadPriority: 0.001];
	while([queueLock.get() lockWhenCondition:1 beforeDate:[NSDate distantFuture]])
	{
		NSAutoreleasePool * pool2 = [[NSAutoreleasePool alloc] init];
		if(!waiting_load.size())
		{
			[pool2 release];
			NSLog(@"ImageCache.workerThread termination");
			[queueLock.get() unlockWithCondition:0];
			return;
		}
		{
			LoadOrSaveStruct op = waiting_load.front();
			waiting_load.pop_front();
			[queueLock.get() unlockWithCondition:waiting_load.size() ? 1 : 0];
			NSString * old_path = 0;
			NSString * path = [self pathForUrl:op.url.get() old:&old_path];
			UIImage * image = 0;
			if( op.data.get() )
			{
				if( LOG )
					NSLog(@"Worker saving image {%@}", op.url.get());
				[op.data.get() writeToFile:path atomically:true];
				if( LOG )
					NSLog(@"Saved to {%@}", path);
				image = [UIImage imageWithData:op.data.get()];
			}
			else {
				if( LOG )
					NSLog(@"Worker loading image {%@}", op.url.get());
				NSError * error = 0;
				[[NSFileManager defaultManager] moveItemAtPath:old_path toPath:path error:&error]; // Do not test for failure
				image = [UIImage imageWithContentsOfFile:path];
			}
			
			//[op loadFromDisk];
			//		[NSThread sleepForTimeInterval:2.0];
			NSArray * arr = [NSArray arrayWithObjects:op.url.get(), image, nil];
			[self performSelectorOnMainThread:@selector(imageLoadedFromDisk:) withObject:arr waitUntilDone:NO];
		}
		[pool2 release];
    }
}

-(void)wait_termination {
	NSLog(@"ImageCache.wait_termination 1");
    [queueLock.get() lock];
	waiting_load.clear();
    [queueLock.get() unlockWithCondition:1];
	NSLog(@"ImageCache.wait_termination 2");
	[queueLock.get() lockWhenCondition:0];
    [queueLock.get() unlock];
	NSLog(@"ImageCache.wait_termination 3");
}

-(void)attempt_failed_downloads {
	while (!failed_download.empty()) {
		NSPtr<NSString> url = failed_download.back();
		waiting_download.push_back( url );
		failed_download.pop_back();
		ImageCacheEntry & ss = memory_cache[url];
		ss.status = DOWNLOADING;
		[self broadcastImageChanged:&ss forUrl:url.get()];
	}
	[self downloadNext];
}

-(void)broadcastImageChanged:(ImageCacheEntry *)entry forUrl:(NSString *)url {
	if( delegate )
		[delegate imageCache:self imageChanged:entry forUrl:url];
	for( ImageCacheDelegates::iterator it = entry->delegates.begin(); it != entry->delegates.end(); ++it )
	{
		[*it imageCache:self imageChanged:entry forUrl:url];
	}
}


-(void)imageLoadedFromDisk:(NSArray *)arr {
	NSString * url = [arr objectAtIndex:0];
	UIImage * image = ([arr count] < 2) ? 0 : [arr objectAtIndex:1]; // image was nil, did not decode from disk or from network
	if( LOG )
		NSLog(@"imageLoaded 1 {%@}", url);
	ImageCacheEntry & ss = memory_cache[url];
	ss.image = image;
	ss.status = LOADED;
	[self broadcastImageChanged:&ss forUrl:url];
}

-(void)downloadComplete:(ImageCacheDownloader *)downloader url:(NSString *)url withData:(NSData *)data {
	passive_downloaders.push_back( downloader );

	std::deque< NSPtr<ImageCacheDownloader> >::iterator it = std::find(active_downloaders.begin(), active_downloaders.end(), passive_downloaders.back());
	if( it != active_downloaders.end() )
		active_downloaders.erase(it);

	ImageCacheEntry & ss = memory_cache[url];

	if( !data )
	{
		ss.status = FAILED_DOWNLOAD;

		failed_download.push_back( url );
	}
	else
	{
		ss.status = LOADING;

		LoadOrSaveStruct op;
		op.url = url;
		op.data = data;
		if( LOG )
			NSLog(@"startLoadingImageforUrl will save&load {%@}", op.url.get());
		[queueLock.get() lock];
		waiting_load.push_back( op );
		[queueLock.get() unlockWithCondition:1];		
	}
	[self broadcastImageChanged:&ss forUrl:url];
	[self downloadNext];
}

-(void)downloadNext {
	if( passive_downloaders.empty() || waiting_download.empty() )
		return;
	active_downloaders.push_back( passive_downloaders.back() );
	passive_downloaders.pop_back();

	NSPtr<NSString> url = waiting_download.back();
	waiting_download.pop_back();

	[active_downloaders.back().get() downloadNext:url.get()];
}


-(void)stopLoadingImageforUrl:(NSString *)url withDelegate:(id<ImageCacheDelegate>)dg {
	if( !url )
		return;
	CacheType::iterator it = memory_cache.find(url);
	if( it == memory_cache.end() )
		return;
	it->second.delegates.remove(dg);
}

-(void)startDownloadingImageforUrl:(NSString *)url {
	[self startLoadingImageforUrl:url withDelegate:0];
}

-(ImageCacheEntry *)startLoadingImageforUrl:(NSString *)url withDelegate:(id<ImageCacheDelegate>)dg {
	if( !url )
		return 0;
	ImageCacheEntry & ss = memory_cache[url];
	if( dg )
		ss.delegates.push_back(dg);
	if( ss.status != JUST_CREATED )
	{
		return &ss;
	}
	NSString * old_path = 0;
	NSString * path = [self pathForUrl:url old:&old_path];
	if( ![[NSFileManager defaultManager] fileExistsAtPath:path] && ![[NSFileManager defaultManager] fileExistsAtPath:old_path] )
	{
		if( LOG )
			NSLog(@"startLoadingImageforUrl will download {%@}", url);
		ss.status = DOWNLOADING;
		waiting_download.push_back( url );
		[self downloadNext];
		return &ss;
	}
	if( LOG )
		NSLog(@"startLoadingImageforUrl will load {%@}", url);
	ss.status = LOADING;
	LoadOrSaveStruct op;
	op.url = url;
	[queueLock.get() lock];
	waiting_load.push_back( op );
	[queueLock.get() unlockWithCondition:1];
	return &ss;
}

@end
*/